package com.authine.cloudpivot.app.openapi.gateway;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.client.utils.JSONUtils;
import com.authine.cloudpivot.app.openapi.domain.gateway.DynamicRouteGateway;
import com.authine.cloudpivot.app.openapi.dto.domainmodel.GatewayRouteDefinition;
import com.authine.cloudpivot.app.openapi.repository.RouteConfigDORepository;
import com.authine.cloudpivot.app.openapi.repository.databaseobject.RouteConfigDO;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author carter
 * create_date  2020/6/5 09:47
 * description     动态网关实现
 */
@Slf4j
@Component
public class DynamicRouteGatewayImpl implements DynamicRouteGateway, ApplicationEventPublisherAware {

    private static final String SUCCESS = "success";

    private ApplicationEventPublisher applicationEventPublisher;

    private final RouteDefinitionWriter routeDefinitionWriter;

    private final RouteConfigDORepository routeConfigDORepository;

    public DynamicRouteGatewayImpl(RouteDefinitionWriter routeDefinitionWriter, RouteConfigDORepository routeConfigDORepository) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeConfigDORepository = routeConfigDORepository;
    }

    @Override
    public String add(GatewayRouteDefinition gatewayRouteDefinition) {
        return add(gatewayRouteDefinition, true);
    }

    @Synchronized
    private String add(GatewayRouteDefinition gatewayRouteDefinition, Boolean saveToDB) {
        log.debug("开始增加路由：{}", gatewayRouteDefinition);

        RouteDefinition routeDefinition = assembleRouteDefinition(gatewayRouteDefinition);


        //尝试解决并发问题
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();

        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));

        if (saveToDB) {
            RouteConfigDO routeConfig = transRouteConfigDO(routeDefinition);
            Optional.ofNullable(gatewayRouteDefinition).map(GatewayRouteDefinition::getMemo).ifPresent(routeConfig::setMemo);
            routeConfigDORepository.save(routeConfig);

            log.debug("新增路由成功！{}", routeConfig.getId());
        }

        return SUCCESS;
    }


    @Override
    public String update(GatewayRouteDefinition gatewayRouteDefinition) {
        delete(gatewayRouteDefinition.getId());
        return add(gatewayRouteDefinition);
    }

    @Override
    public String delete(String id) {
        return delete(id, true);
    }

    @Synchronized
    private String delete(String id, boolean saveToDB) {
        try {
            log.info("开始删除路由id:{}", id);
            routeDefinitionWriter.delete(Mono.just(id));

            if (saveToDB) {
                RouteConfigDO routeConfigDO = routeConfigDORepository.findBy("routeId", id);
                if (Objects.nonNull(routeConfigDO) && Objects.nonNull(routeConfigDO.getId()) && routeConfigDO.getId() > 0) {
                    routeConfigDORepository.deleteById(routeConfigDO.getId());
                }
            }
            log.info("删除路由{}成功：", id);
        } catch (Exception ex) {
            log.info("删除路由{}失败：", id, ex);
            return "fail";
        }

        return SUCCESS;
    }

    /**
     * 查询所有的动态路由配置
     *
     * @return
     */
    @Override
    public List<GatewayRouteDefinition> findAll() {

        List<RouteConfigDO> routeConfigDOList = routeConfigDORepository.findAll();

        return routeConfigDOList.stream().map(item -> {
            GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition();

            gatewayRouteDefinition.setId(item.getRouteId());
            gatewayRouteDefinition.setOrder(item.getRouteOrder());
            gatewayRouteDefinition.setUri(item.getRouteUri());
            gatewayRouteDefinition.setMemo(item.getMemo());

            Map map = JSONObject.parseObject(item.getRouteMetadata(), Map.class);
            gatewayRouteDefinition.setMetadata(map);
            gatewayRouteDefinition.setPredicates(JSONObject.parseArray(item.getRoutePredicates(), GatewayRouteDefinition.GatewayPredicateDefinition.class));
            gatewayRouteDefinition.setFilters(JSONObject.parseArray(item.getRouteFilters(), GatewayRouteDefinition.GatewayFilterDefinition.class));

            return gatewayRouteDefinition;
        }).collect(Collectors.toList());
    }

    /**
     * 启动的时候从数据库初始化路由配置
     */
    @Override
    public void initRoute() {
        log.info("开始初始化路由，从数据库中加载所有预设的路由");
        List<GatewayRouteDefinition> gatewayRouteDefinitionList = findAll();

        Optional.ofNullable(gatewayRouteDefinitionList)
                .orElse(Collections.emptyList())
                .forEach(gatewayRouteDefinition -> {
                    String id = gatewayRouteDefinition.getId();
                    delete(id, false);
                    log.info("开始清除内存中的路由：{}", id);
                    add(gatewayRouteDefinition, false);
                    log.info("开始新增路由到内存中：{}", id);
                });
    }


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gatewayRouteDefinition) {
        RouteDefinition definition = new RouteDefinition();

        // ID
        Optional.ofNullable(gatewayRouteDefinition)
                .map(GatewayRouteDefinition::getId)
                .ifPresent(definition::setId);
        // ORDER
        Optional.ofNullable(gatewayRouteDefinition)
                .map(GatewayRouteDefinition::getOrder)
                .ifPresent(definition::setOrder);
        // URI
        Optional.ofNullable(gatewayRouteDefinition)
                .map(GatewayRouteDefinition::getUri)
                .map(uri -> UriComponentsBuilder.fromUriString(uri).build().toUri())
                .ifPresent(definition::setUri);

        // METADATA
        Optional.ofNullable(gatewayRouteDefinition).map(GatewayRouteDefinition::getMetadata)
                .ifPresent(definition.getMetadata()::putAll);

        // Predicates
        List<PredicateDefinition> pdList = Optional.ofNullable(gatewayRouteDefinition).map(GatewayRouteDefinition::getPredicates)
                .orElse(Collections.emptyList())
                .stream()
                .map(gatewayPredicateDefinition -> {
                    PredicateDefinition predicate = new PredicateDefinition();
                    predicate.setArgs(gatewayPredicateDefinition.getArgs());
                    predicate.setName(gatewayPredicateDefinition.getName());
                    return predicate;
                }).collect(Collectors.toList());
        definition.setPredicates(pdList);

        // Filters
        List<FilterDefinition> fdList = Optional.ofNullable(gatewayRouteDefinition).map(GatewayRouteDefinition::getFilters)
                .orElse(Collections.emptyList())
                .stream()
                .map(gatewayFilterDefinition -> {
                    FilterDefinition filter = new FilterDefinition();
                    filter.setArgs(gatewayFilterDefinition.getArgs());
                    filter.setName(gatewayFilterDefinition.getName());
                    return filter;
                }).collect(Collectors.toList());

        definition.setFilters(fdList);

        return definition;
    }


    private RouteConfigDO transRouteConfigDO(RouteDefinition routeDefinition) {
        RouteConfigDO routeConfigDO = new RouteConfigDO();

        Optional.ofNullable(routeDefinition).map(RouteDefinition::getId).ifPresent(routeConfigDO::setRouteId);
        Optional.ofNullable(routeDefinition).map(RouteDefinition::getOrder).ifPresent(routeConfigDO::setRouteOrder);
        Optional.ofNullable(routeDefinition).map(RouteDefinition::getUri).map(URI::toString).ifPresent(routeConfigDO::setRouteUri);
        Optional.ofNullable(routeDefinition).map(RouteDefinition::getMetadata).map(this::getJsonString).ifPresent(routeConfigDO::setRouteMetadata);
        Optional.ofNullable(routeDefinition).map(RouteDefinition::getPredicates).map(this::getJsonString).ifPresent(routeConfigDO::setRoutePredicates);
        Optional.ofNullable(routeDefinition).map(RouteDefinition::getFilters).map(this::getJsonString).ifPresent(routeConfigDO::setRouteFilters);

        return routeConfigDO;
    }

    private String getJsonString(Object item) {
        try {
            return JSONUtils.serializeObject(item);
        } catch (IOException e) {
            log.error("json序列化错误", e);
        }
        return null;
    }


}
